#include "mmap.hpp"

#ifdef _WIN32

MMAP::MMAP(const void* file_path, size_t create_size, bool write_access) {
    m_mapped_ptr = nullptr;
    
    DWORD access_mode = GENERIC_READ;
    DWORD share_mode = FILE_SHARE_READ;
    DWORD page_protection = PAGE_READONLY;
    DWORD map_access = FILE_MAP_READ;
    DWORD creation_disposition = OPEN_EXISTING;
    
    if (create_size > 0) {
        creation_disposition = CREATE_ALWAYS;
    }
    
    if (create_size > 0 || write_access) {
        access_mode |= GENERIC_WRITE;
        share_mode = 0;
        page_protection = PAGE_READWRITE;
        map_access = FILE_MAP_ALL_ACCESS;
    }
    
    m_file_handle = CreateFileW(
        static_cast<LPCWSTR>(file_path),
        access_mode,
        share_mode,
        NULL,
        creation_disposition,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (m_file_handle == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to open file: " << GetLastError() << std::endl;
        return;
    }
    
    if (create_size > 0) {
        m_file_size.LowPart = static_cast<DWORD>(create_size);
        m_file_size.HighPart = static_cast<DWORD>(create_size >> 32);
    } else {
        if (!GetFileSizeEx(m_file_handle, &m_file_size)) {
            std::cerr << "Failed to get file size: " << GetLastError() << std::endl;
            CloseHandle(m_file_handle);
            return;
        }
    }

    m_mapping_handle = CreateFileMapping(
        m_file_handle,
        NULL,
        page_protection,
        m_file_size.HighPart,
        m_file_size.LowPart,
        NULL
    );

    if (m_mapping_handle == NULL) {
        std::cerr << "Failed to create file mapping: " << GetLastError() << std::endl;
        CloseHandle(m_file_handle);
        return;
    }

    m_mapped_ptr = MapViewOfFile(
        m_mapping_handle,
        map_access,
        0,
        0,
        create_size > 0 ? create_size : 0
    );

    if (m_mapped_ptr == NULL) {
        std::cerr << "Failed to map view of file: " << GetLastError() << std::endl;
        CloseHandle(m_mapping_handle);
        CloseHandle(m_file_handle);
    }
}

MMAP::~MMAP() {
    if (m_mapped_ptr != nullptr) {
        UnmapViewOfFile(m_mapped_ptr);
    }
    if (m_mapping_handle != NULL) {
        CloseHandle(m_mapping_handle);
    }
    if (m_file_handle != NULL && m_file_handle != INVALID_HANDLE_VALUE) {
        CloseHandle(m_file_handle);
    }
}

#else

MMAP::MMAP(const void* file_path, size_t create_size, bool write_access) {
    m_mapped_ptr = nullptr;
    m_file_descriptor = -1;
    
    if (create_size > 0) write_access = true;
    
    int open_flags = O_RDWR;
    if (!write_access) {
        open_flags = O_RDONLY;
    }

    if (create_size > 0) {
        m_file_descriptor = open(static_cast<const char*>(file_path), 
                                O_CREAT | O_TRUNC | O_RDWR, 
                                S_IRUSR | S_IWUSR);
        if (m_file_descriptor == -1) {
            throw std::runtime_error("Failed to open file for writing");
        }

        if (ftruncate(m_file_descriptor, create_size) == -1) {
            close(m_file_descriptor);
            throw std::runtime_error("Failed to set file size");
        }

        m_file_size = create_size;
    } else {
        m_file_descriptor = open(static_cast<const char*>(file_path), open_flags);
        if (m_file_descriptor == -1) {
            throw std::runtime_error("Failed to open file");
        }

        struct stat file_stat;
        if (fstat(m_file_descriptor, &file_stat) == -1) {
            close(m_file_descriptor);
            throw std::runtime_error("Failed to get file size");
        }

        m_file_size = file_stat.st_size;
    }

    int protection = PROT_READ;
    if (write_access) protection |= PROT_WRITE;
    
    m_mapped_ptr = mmap(0, m_file_size, protection, MAP_SHARED, m_file_descriptor, 0);
    if (m_mapped_ptr == MAP_FAILED) {
        close(m_file_descriptor);
        throw std::runtime_error("Failed to map file to memory");
    }
}

MMAP::~MMAP() {
    if (m_mapped_ptr != nullptr) {
        munmap(m_mapped_ptr, m_file_size);
    }
    if (m_file_descriptor != -1) {
        close(m_file_descriptor);
    }
}

#endif

void* MMAP::getPointer() {
    return m_mapped_ptr;
}

const void* MMAP::getPointer() const {
    return m_mapped_ptr;
}

size_t MMAP::getSize() const {
#ifdef _WIN32
    return (static_cast<size_t>(m_file_size.HighPart) << 32) + m_file_size.LowPart;
#else
    return m_file_size;
#endif
}

char& MMAP::operator[](size_t index) {
    if (index >= getSize()) {
        throw std::out_of_range("MMAP index out of range");
    }
    return static_cast<char*>(m_mapped_ptr)[index];
}

const char& MMAP::operator[](size_t index) const {
    if (index >= getSize()) {
        throw std::out_of_range("MMAP index out of range");
    }
    return static_cast<const char*>(m_mapped_ptr)[index];
}